package com.share51.novel.service.impl;

import com.google.common.collect.Lists;
import com.share51.novel.common.StatusConstant;
import com.share51.novel.common.ViewConstant;
import com.share51.novel.common.enums.CrawlTypeEnum;
import com.share51.novel.common.enums.NovelSiteEnums;
import com.share51.novel.common.parse.NovelParse;
import com.share51.novel.common.utils.ZipUtil;
import com.share51.novel.config.NovelConfig;
import com.share51.novel.dao.CrawlMapper;
import com.share51.novel.entity.Chapter;
import com.share51.novel.entity.Crawl;
import com.share51.novel.entity.Novel;
import com.share51.novel.init.ApplicationCached;
import com.share51.novel.service.CrawlService;
import com.share51.novel.service.HttpClientService;
import com.share51.novel.service.NovelService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.io.*;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 爬虫业务处理类
 * @author gb
 */
@Slf4j
@Service("crawlService")
public class CrawlServiceImpl implements CrawlService {

    @Resource
    private NovelConfig novelConfig;

    @Resource
    private HttpClientService httpClientService;

    @Resource
    private NovelService novelService;

    @Resource
    private CrawlMapper crawlMapper;

    @Override
    public void addCrawl(String url, Integer type,Integer site){
        if (ApplicationCached.SERVER_IS_INIT){
            throw new IllegalArgumentException("服务器正在维护数据，请稍后访问");
        }
        if (!NovelSiteEnums.exist(site)){
            throw new IllegalArgumentException("参数错误，不支持的站点，site="+site);
        }
        if (!CrawlTypeEnum.exist(type)){
            throw new IllegalArgumentException("参数错误，站点类型不存在，type="+type);
        }
        long startTime=System.currentTimeMillis();
        log.debug("开始处理爬虫业务，采集url:{},处理类型：{}",url,CrawlTypeEnum.of(type).getName());
        try {
            switch (CrawlTypeEnum.of(type)){
                case NOVEL:
                    Crawl crawl = new Crawl();
                    crawl.setUrl(url);
                    crawlNovel(crawl,site);
                    break;
                case CATEGORY:
                    String html = getHtml(url);
                    crawlCategory(html,site);
                    break;
                    default:
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        long endTime=System.currentTimeMillis();
        log.debug("爬虫处理完成，耗时：{}s",(endTime-startTime)/1000);
    }

    private String getHtml(String url) throws IOException{
        return httpClientService.doGet(url);
    }

    /**
     * 爬取分类数据
     * @param html 网页内容
     * @param site 站点（爬虫规则）
     * @throws IOException
     */
    private void crawlCategory(String html,Integer site) throws IOException {
        String hostName=getCrawlHostName(site);
        List<Crawl> list = NovelParse.parseCategory(html, NovelSiteEnums.of(site));
        for (Crawl crawl:list){
            String crawlUrl = crawl.getUrl().replace(hostName,"");
            if (ApplicationCached.existCrawlUrl(site+":"+crawlUrl)){
                log.debug("当前url已经采集过了,url：{}",crawl.getUrl());
                // 过滤器验证 认为存在了此url
                continue;
            }
            Novel novel = crawl.getNovel();
            novel.setStatus(StatusConstant.NovelStatus.NOVEL_WAIT_PUBLISH);
            Integer newNovel = novelService.save(novel);
            crawl.setNovelId(novel.getId());
            crawl.setNovel(null);
            crawl.setSite(site);
            crawl.setStatus(StatusConstant.STATUS_OK);
            crawl.setUrl(crawlUrl);
            crawlMapper.insert(crawl);
            // 将录入数据库中的数据 写入过滤器中
            ApplicationCached.addCrawlUrl(site+":"+crawlUrl);
            this.crawlNovel(crawl,site);
        }
    }

    private String getCrawlHostName(int site){
       return novelConfig
                .getCrawlSite()
                .get(NovelSiteEnums
                        .of(site)
                        .getEnCode())
                .getUrl();
    }

    private void crawlNovel(Crawl crawl,Integer site) throws IOException {
        String hostName=getCrawlHostName(site);
        String url =hostName + crawl.getUrl();
        String html = getHtml(url);
        // 解析 html
        Novel novel = NovelParse.parseNovel(html, NovelSiteEnums.of(site),url,hostName);
        if (novel==null){
            throw new RuntimeException("解析小说详情失败");
        }
        if (crawl.getNovelId()!=null){
            novel.setId(crawl.getNovelId());
        }
        String icon=downloadIcon(novel.getIcon(),novel.getId());
        novel.setIcon(icon);
        novelService.update(novel);
        this.writeNovelToTxt(novel);
        novelService.saveChapter(novel.getChapterList(),novel.getId(),hostName);
        this.addChapterUrl(novel.getChapterList(),hostName,site);
        if (crawl.getNovelId()==null){
            crawl.setNovelId(novel.getId());
            crawlMapper.insert(crawl);
            ApplicationCached.addCrawlUrl(site+":"+crawl.getUrl());
        }
    }

    private void writeNovelToTxt(Novel novel) throws IOException {
        if (novelConfig.isEnableWriteTxt()) {
            File fileDir = new File(novelConfig.getSaveFilePath() + ViewConstant.NOVEL_SAVE_URL);
            if (!fileDir.exists()) {
                boolean mkdirs = fileDir.mkdirs();
            }
            File file = new File(fileDir, novel.getId() + ".txt");
            try (FileOutputStream fos = new FileOutputStream(file)) {
                String reliefAffirming = novelConfig.getReliefAffirming();
                String name = novel.getName();
                String author = novel.getAuthor();
                String introduce = novel.getIntroduce();
                List<String> list = Lists.newArrayList();
                list.add(reliefAffirming);
                list.add("书名：《" + name + "》");
                list.add("作者：" + author);
                list.add("摘要内容：" + introduce);
                list.add("以下是小说正文：");
                IOUtils.writeLines(list, "\r\n\r\n", fos, "UTF-8");
                ZipUtil.zip(file.getAbsolutePath(), true);
            }
        }
    }

    private void addChapterUrl(List<Chapter> chapterList,String hostName,int site){
        List<String> collect = chapterList
                .stream()
                .map(chapter -> site+":"+chapter.getUrl().replace(hostName, ""))
                .collect(Collectors.toList());
        ApplicationCached.addAllChapterUrl(collect);
    }

    /**
     * 下载封面
     * @param url
     * @return
     */
    private String downloadIcon(String url,Integer id) throws IOException{
        if (StringUtils.isBlank(url)){
            throw new IOException("下载小说封面失败，小说封面url为空");
        }
        String suffix=url.substring(url.lastIndexOf("."),url.length());
        String dirPath=File.separatorChar+"icon"+createDir();
        File fileDir=new File(novelConfig.getSaveFilePath()+dirPath);
        if (!fileDir.exists()){
            boolean mkdirs = fileDir.mkdirs();
        }
        String fileName=id+"_s"+suffix;
        File iconFile = new File(fileDir,fileName);

        try(
                ByteArrayOutputStream bos=httpClientService.downloadFile(url);
                ByteArrayInputStream bis=new ByteArrayInputStream(bos.toByteArray());
                FileOutputStream fileOutputStream=new FileOutputStream(iconFile);
            ) {
            IOUtils.copy(bis, fileOutputStream);
            log.debug("小说封面下载成功，小说id:{}，存放目录：{}",id,iconFile.getAbsolutePath());
        } catch (IOException e) {
            log.error("小说封面下载失败，封面url:",url,e);
        }
        return dirPath+fileName;
    }

    /**
     * 创建文件存放的目录
     * @return
     */
    private String createDir(){
        Calendar calendar=Calendar.getInstance();
        int month=calendar.get(Calendar.MONTH);
        return File.separatorChar
                + ""
                + calendar.get(Calendar.YEAR)
                + File.separatorChar
                + (month<10?"0"+month:month)
                + File.separatorChar;
    }

    @Override
    public List<String> getAllCrawlUrlAndSite() {
        List<Crawl> allCrawlUrlAndSite = this.getAllCrawlUrlAndSiteAndId();
        return allCrawlUrlAndSite
                .stream()
                .map(crawl->crawl.getSite()+":"+crawl.getUrl())
                .collect(Collectors.toList());
    }

    @Override
    public List<Crawl> getAllCrawlUrlAndSiteAndId() {
        List<Crawl> allCrawlUrlAndSite = crawlMapper.getAllCrawlUrlAndSite();
        if (allCrawlUrlAndSite==null||allCrawlUrlAndSite.isEmpty()){
            return Collections.emptyList();
        }
        return allCrawlUrlAndSite;
    }
}
